##############
# (*) Kaleidoscope
# By: Allan Spale
# July 2008

from Kaleidoscope.KSColumnBSDDBUnique import KSColumnBSDDBUnique
from Kaleidoscope.KSColumnBSDDBDuplicate import KSColumnBSDDBDuplicate
from Kaleidoscope.KSColumnFactory import KSColumnFactory, KSColumnFactoryError
from Kaleidoscope.KSColumn import KSColumn

import os.path
import os
from bsddb import db


class KSColumnFactoryBSDDB(KSColumnFactory):
    """
    In order to create a single point of creating KSColumn subclasses in a
    somewhat generic way, a factory class will be created. This will hopefully
    reduce the number of classes that need to be re-written for each datastore
    and motivate people to port their favorite datastores to Kaleidoscope by
    making it relatively tolerable.

    This class will be overridden in other classes to do the same thing for
    other datastores.

    """
    @property
    def env(self):
        """
        Return the BSDDB environment reference.

        """
        return self.__env

    @property
    def is_open(self):
        return self.__b_open



    def __init__(self):
        try:
            self.__b_open = False

            self.__env = db.DBEnv(0)
            self.__u_home_directory = ''

            # Default flags setting...added DB_PRIVATE to get more memory
            # since the database is not going to be multi-threaded at this
            # time.
            self.__i_flags = db.DB_CREATE | db.DB_INIT_MPOOL | db.DB_INIT_LOCK \
                | db.DB_INIT_LOG | db.DB_INIT_TXN | db.DB_PRIVATE

            u_home_directory = property(fget=self.get_home_directory,
                                        fset=self.set_home_directory)

            i_flags = property(fget=self.__get_i_flags,
                               fset=self.__set_i_flags)

        except Exception:
            KSColumnFactoryError.print_and_raise_error(u'An error occurred ',
                u'while trying to create the BSDDB environment.',
                KSColumnFactoryError.B_UNKNOWN_EXCEPTION)


    def open(self):
        try:
            if not (self.__b_open):
                # 500 MB cache
                self.__env.set_cachesize(0, 500000000, 5)
                self.__b_open = True
                return self.__env.open(
                    self.__u_home_directory, self.__i_flags, 0)
        except Exception:
            KSColumnFactoryError.print_and_raise_error(u'A problem occurred ',
                u'when trying to open the BSDDB environment.',
                KSColumnFactoryError.B_UNKNOWN_EXCEPTION)

    def close(self):
        try:
            if (self.__b_open):
                self.__b_open = False
                return self.__env.close()
        except Exception:
            KSColumnFactoryError.print_and_raise_error(u'A problem occurred ',
                u'when trying to close the BSDDB environment.',
                KSColumnFactoryError.B_UNKNOWN_EXCEPTION)


    def checkpoint(self):
        """
        Create a checkpoint if 1 MB has been written.

        """
        try:
            self.__env.txn_checkpoint(kbyte=1000)
        except Exception:
            KSColumnFactoryError.print_and_raise_error(u'A problem occurred ',
                u'while trying to checkpoint the BSDDB environment.',
                KSColumnFactoryError.B_UNKNOWN_EXCEPTION)


    def create_column(self, i_column_type, *args):
        """
        Changes are not clean, but this prevents from having to rewrite a lot
        of code in many files. For unique columns, args[5] will be rechecked
        to know if b_unique_values should be set to B_UNIQUE_VALUES (for the
        case of I_UNIQUE_COL_UNIQUE_VALS) or B_DUPLICATE_VALUES (for the case
        of I_UNIQUE_COL_DUPLICATE_VALS. In the case of I_DUPLICATE_COL, the
        variable b_unique_values will always be set to B_DUPLICATE_VALUES.

        """
        if (isinstance(i_column_type, int)):
            if (i_column_type == KSColumnFactory.I_UNIQUE_COL_UNIQUE_VALS):
                return self.new_ks_column_unique_unique_values(*args)
            elif (i_column_type == KSColumnFactory.I_UNIQUE_COL_DUPLICATE_VALS):
                return self.new_ks_column_unique_duplicate_values(*args)
            elif (i_column_type == KSColumnFactory.I_DUPLICATE_COL):
                return self.new_ks_column_duplicate_duplicate_values(*args)
            elif (i_column_type == KSColumnFactory.I_VIRTUAL_COL):
                return self.new_ks_column_virtual_values(*args)
            else:
                return None
        else:
            KSColumnFactoryError.print_and_raise_error(u'The column type must ',
                + u'be an integer.', KSColumnFactoryError.B_UNKNOWN_EXCEPTION)


    def new_ks_column_unique_unique_values(self, *args):
        return KSColumnBSDDBUnique(args[0], args[1], args[2],
            self.__env, args[3], args[4], KSColumn.B_UNIQUE_VALUES)

    def new_ks_column_unique_duplicate_values(self, *args):
        return KSColumnBSDDBUnique(args[0], args[1], args[2],
            self.__env, args[3], args[4], KSColumn.B_DUPLICATE_VALUES)

    def new_ks_column_duplicate_duplicate_values(self, *args):
        return KSColumnBSDDBDuplicate(args[0], args[1], args[2],
            self.__env, args[3], args[4])

    def new_ks_column_virtual_values(self, *args):
        return KSColumnBSDDBDuplicate(args[0])


    def delete_column_from_filesystem(self, u_column_filename):
        """
        When the column has been deleted from the system, it should be deleted
        from the local filesystem. There is a problematic case with not
        tracking how many open references there are to this column. This needs
        to be fixed later.

        To reduce interdependencies on classes, avoid a tight coupling with
        KSSysColumn (instead rely on KSSysColumn to call KSColumnFactory).

        """
        try:
            u_absolute_filename = os.path.join(
                self.__u_home_directory, u_column_filename)
            return os.remove(u_absolute_filename)
        except Exception:
            KSColumnFactoryError.print_and_raise_error(u'A problem occurred ',
                + u'when trying to delete %s from the filesystem. Make sure '
                + u'that all references to this column file have been closed '
                + u'before trying to issue this command again.' \
                % u_absolute_filename, KSColumnFactoryError.B_UNKNOWN_EXCEPTION)


    def get_home_directory(self):
        """
        Property method for retrieving the property value of u_home_directory.

        """
        return self.__u_home_directory

    def set_home_directory(self, u_directory):
        """
        Property method for setting the property value of u_home_directory.

        """
        print 'set home directory to: ', u_directory
        if (isinstance(u_directory, unicode)):
            if (os.path.isdir(u_directory)):
                self.__u_home_directory = u_directory
                self.__env.set_data_dir(self.__u_home_directory)
            else:
                KSColumnFactoryError.print_and_raise_error(u'The directory '
                    u'must currently exist in the filesystem.',
                    KSColumnFactoryError.B_CLASS_EXCEPTION)
        else:
            KSColumnFactoryError.print_and_raise_error(u'The directory must ',
                u'be a valid unicode string.',
                KSColumnFactoryError.B_CLASS_EXCEPTION)


    def __get_i_flags(self):
        """
        Property method for retrieving the property value of i_flags.

        """
        return self.__i_flags

    def __set_i_flags(self, i_flags):
        """
        Property method for setting the property value of i_flags.

        """
        if (isinstance(i_flags, int)):
            self.__i_flags = i_flags
        else:
            KSColumnFactoryError.print_and_raise_error(u'The directory must ',
                u'be a valid unicode string.',
                KSColumnFactoryError.B_CLASS_EXCEPTION)